home *** CD-ROM | disk | FTP | other *** search
- ┌──────────────────────[ Rage Technologies, Inc. ]─────────────────────────┐
- │ │
- │ - Members - │
- │ ] Myth: Ideas / Coder [ │
- │ ] Night Stalker: Coder [ │
- │ ] SKoRPiON: Musician [ │
- │ │
- │ - Support Board - │
- │ ] Shadow Lands: (407) 851-2313, run by Night Stalker [ │
- │ │
- ──┴──────────────────────────────────────────────────────────────────────────┴──
- How to program the DMA - by Night Stalker
- ────────────────────────────────────────────────────────────────────────────────
-
- What is the DMA?
-
- The DMA is another chip on your motherboard (usually is an Intel 8237
- chip) that allows you (the programmer) to offload data transfers between
- I/O boards. DMA actually stands for 'Direct Memory Access'.
-
- An example of DMA usage would be the Sound Blaster's ability to play
- samples in the background. The CPU sets up the sound card and the DMA. When
- the DMA is told to 'go', it simply shovels the data from RAM to the card.
- Since this is done off-CPU, the CPU can do other things while the data is
- being transferred.
-
- Enough basics. Here's how you program the DMA chip.
-
- ────────────────────────────────────────────────────────────────────────────────
-
- When you want to start a DMA transfer, you need to know three things:
-
- - Where the memory is located (what page),
- - The offset into the page, and
- - How much you want to transfer.
-
- Since the DMA can work in both directions (memory to I/O card, and I/O
- card to memory), you can see how the Sound Blaster can record as well as
- play by using DMA.
-
- The DMA has two restrictions which you must abide by:
-
- - You cannot transfer more than 64K of data in one shot, and
- - You cannot cross a page boundary.
-
- Restriction #1 is rather easy to get around. Simply transfer the first
- block, and when the transfer is done, send the next block.
-
- For those of you not familiar with pages, I'll try to explain.
-
- Picture the first 1MB region of memory in your system. It is divided
- into 16 pages of 64K a piece like so:
-
- Page Segment:Offset address
- ---- ----------------------
- 0 0000:0000 - 0000:FFFF
- 1 1000:0000 - 1000:FFFF
- 2 2000:0000 - 2000:FFFF
- 3 3000:0000 - 3000:FFFF
- 4 4000:0000 - 4000:FFFF
- 5 5000:0000 - 5000:FFFF
- 6 6000:0000 - 6000:FFFF
- 7 7000:0000 - 7000:FFFF
- 8 8000:0000 - 8000:FFFF
- 9 9000:0000 - 9000:FFFF
- A A000:0000 - A000:FFFF
- B B000:0000 - B000:FFFF
- C C000:0000 - C000:FFFF
- D D000:0000 - D000:FFFF
- E E000:0000 - E000:FFFF
- F F000:0000 - F000:FFFF
-
- This might look a bit overwhelming. Not to worry if you're a C
- programmer, as I'm going to assume you know the C language for the examples
- in this text. All the code in here will compile with Turbo C 2.0.
-
- Okay, remember the three things needed by the DMA? Look back if you
- need to. We can stuff this data into a structure for easy accessing:
-
- typedef struct
- {
- char page;
- unsigned int offset;
- unsigned int length;
- } DMA_block;
-
- Now, how do we find a memory pointer's page and offset? Easy. Use
- the following code:
-
- void LoadPageAndOffset(DMA_block *blk, char *data)
- {
- unsigned int temp, segment, offset;
- unsigned long foo;
-
- segment = FP_SEG(data);
- offset = FP_OFF(data);
-
- blk->page = (segment & 0xF000) >> 12;
- temp = (segment & 0x0FFF) << 4;
- foo = offset + temp;
- if (foo > 0xFFFF)
- blk->page++;
- blk->offset = (unsigned int)foo;
- }
-
- Most (if not all) of you are probably thinking, "What the heck is he doing
- there?" I'll explain.
-
- The FP_SEG and FP_OFF macros find the segment and the offset of the data
- block in memory. Since we only need the page (look back at the table above),
- we can take the upper 4 bits of the segment to create our page.
-
- The rest of the code takes the segment, adds the offset, and sees if the
- page needs to be advanced or not. (Note that a memory region can be located at
- 2FFF:F000, and a single byte increase will cause the page to increase by one.)
-
- In plain English, the page is the highest 4 bits of the absolute 20 bit
- address of our memory location. The offset is the lower 12 bits of the
- absolute 20 bit address plus our offset.
-
- Now that we know where our data is, we need to find the length.
-
- The DMA has a little quirk on length. The true length sent to the DMA
- is actually length + 1. So if you send a zero length to the DMA, it actually
- transfers one byte, whereas if you send 0xFFFF, it transfers 64K. I guess
- they made it this way because it would be pretty senseless to program the
- DMA to do nothing (a length of zero), and in doing it this way, it allowed a
- full 64K span of data to be transferred.
-
- Now that you know what to send to the DMA, how do you actually start it?
- This enters us into the different DMA channels.
-
- ────────────────────────────────────────────────────────────────────────────────
-
- The DMA has 4 different channels to send 8-bit data. These channels are
- 0, 1, 2, and 3, respectively. You can use any channel you want, but if you're
- transferring to an I/O card, you need to use the same channel as the card.
- (ie: Sound Blaster uses DMA channel 1 as a default.)
-
- There are 3 ports that are used to set the DMA channel:
-
- - The page register,
- - The address (or offset) register, and
- - The word count (or length) register.
-
- The following chart will describe each channel and it's corresponding
- port number:
-
- DMA Channel Page Address Count
- ────────────────────────────────────
- 0 87h 0h 1h
- 1 83h 2h 3h
- 2 81h 4h 5h
- 3 82h 6h 7h
- 4 8Fh C0h C2h
- 5 8Bh C4h C6h
- 6 89h C8h CAh
- 7 8Ah CCh CEh
-
- (Note: Channels 4-7 are 16-bit DMA channels. See below for more info.)
-
-
- Since you need to send a two-byte value to the DMA (the offset and the
- length are both two bytes), the DMA requests you send the low byte of data
- first, then the high byte. I'll give a thorough example of how this is done
- momentarily.
-
- The DMA has 3 registers for controlling it's state. Here is the bitmap
- layout of how they are accessed:
-
- Mask Register (0Ah):
- ────────────────────
-
- MSB LSB
- x x x x x x x x
- ─────────┬───────── ┬ ──┬──
- │ │ │ 00 - Select channel 0 mask bit
- │ │ └──── 01 - Select channel 1 mask bit
- │ │ 10 - Select channel 2 mask bit
- │ │ 11 - Select channel 3 mask bit
- │ │
- │ └────────── 0 - Clear mask bit
- │ 1 - Set mask bit
- │
- └─────────────────────── xx - Don't care
-
-
- Mode Register (0Bh):
- ────────────────────
-
- MSB LSB
- x x x x x x x x
- ──┬── ┬ ┬ ──┬── ──┬──
- │ │ │ │ │ 00 - Channel 0 select
- │ │ │ │ └──── 01 - Channel 1 select
- │ │ │ │ 10 - Channel 2 select
- │ │ │ │ 11 - Channel 3 select
- │ │ │ │
- │ │ │ │ 00 - Verify transfer
- │ │ │ └──────────── 01 - Write transfer
- │ │ │ 10 - Read transfer
- │ │ │
- │ │ └──────────────────── 0 - Autoinitialized
- │ │ 1 - Non-autoinitialized
- │ │
- │ └──────────────────────── 0 - Address increment select
- │
- │ 00 - Demand mode
- └────────────────────────────── 01 - Single mode
- 10 - Block mode
- 11 - Cascade mode
-
-
- DMA clear selected channel (0Ch):
- ─────────────────────────────────
-
- Outputting a zero to this port stops all DMA processes that are currently
- happening as selected by the mask register (0Ah).
-
- ────────────────────────────────────────────────────────────────────────────────
-
- Some of the most common modes to program the mode register are:
-
- - 45h: Write transfer (I/O card to memory), and
- - 49h: Read transfer (memory to I/O card).
-
- Both of these assume DMA channel 1 for all transfers.
-
- Now, there's also the 16-bit DMA channels as well. These shove two bytes
- of data at a time. That's how the Sound Blaster 16 works as well in 16-bit
- mode.
-
- Programming the DMA for 16-bits is just as easy as 8 bit transfers. The
- only difference is you send data to different I/O ports. The 16-bit DMA also
- uses 3 other control registers as well:
-
- Mask Register (D4h):
- ────────────────────
-
- MSB LSB
- x x x x x x x x
- ─────────┬───────── ┬ ──┬──
- │ │ │ 00 - Select channel 4 mask bit
- │ │ └──── 01 - Select channel 5 mask bit
- │ │ 10 - Select channel 6 mask bit
- │ │ 11 - Select channel 7 mask bit
- │ │
- │ └────────── 0 - Clear mask bit
- │ 1 - Set mask bit
- │
- └─────────────────────── xx - Don't care
-
-
- Mode Register (D6h):
- ────────────────────
-
- MSB LSB
- x x x x x x x x
- ──┬── ┬ ┬ ──┬── ──┬──
- │ │ │ │ │ 00 - Channel 4 select
- │ │ │ │ └──── 01 - Channel 5 select
- │ │ │ │ 10 - Channel 6 select
- │ │ │ │ 11 - Channel 7 select
- │ │ │ │
- │ │ │ │ 00 - Verify transfer
- │ │ │ └──────────── 01 - Write transfer
- │ │ │ 10 - Read transfer
- │ │ │
- │ │ └──────────────────── 0 - Autoinitialized
- │ │ 1 - Non-autoinitialized
- │ │
- │ └──────────────────────── 0 - Address increment select
- │
- │ 00 - Demand mode
- └────────────────────────────── 01 - Single mode
- 10 - Block mode
- 11 - Cascade mode
-
-
- DMA clear selected channel (D8h):
- ─────────────────────────────────
-
- Outputting a zero to this port stops all DMA processes that are currently
- happening as selected by the mask register (D4h).
-
-
- Now that you know all of this, how do you actually use it? Here is sample
- code to program the DMA using our DMA_block structure we defined before.
-
- ────────────────────────────────────────────────────────────────────────────────
-
- /* Just helps in making things look cleaner. :) */
- typedef unsigned char uchar;
- typedef unsigned int uint;
-
- /* Defines for accessing the upper and lower byte of an integer. */
- #define LOW_BYTE(x) (x & 0x00FF)
- #define HI_BYTE(x) ((x & 0xFF00) >> 8)
-
- /* Quick-access registers and ports for each DMA channel. */
- uchar MaskReg[8] = { 0x0A, 0x0A, 0x0A, 0x0A, 0xD4, 0xD4, 0xD4, 0xD4 };
- uchar ModeReg[8] = { 0x0B, 0x0B, 0x0B, 0x0B, 0xD6, 0xD6, 0xD6, 0xD6 };
- uchar ClearReg[8] = { 0x0C, 0x0C, 0x0C, 0x0C, 0xD8, 0xD8, 0xD8, 0xD8 };
-
- uchar PagePort[8] = { 0x87, 0x83, 0x81, 0x82, 0x8F, 0x8B, 0x89, 0x8A };
- uchar AddrPort[8] = { 0x00, 0x02, 0x04, 0x06, 0xC0, 0xC4, 0xC8, 0xCC };
- uchar CountPort[8] = { 0x01, 0x03, 0x05, 0x07, 0xC2, 0xC6, 0xCA, 0xCE };
-
- void StartDMA(uchar DMA_channel, DMA_block *blk, uchar mode)
- {
- /* First, make sure our 'mode' is using the DMA channel specified. */
- mode |= DMA_channel;
-
- /* Don't let anyone else mess up what we're doing. */
- disable();
-
- /* Set up the DMA channel so we can use it. This tells the DMA */
- /* that we're going to be using this channel. (It's masked) */
- outportb(MaskReg[DMA_channel], 0x04 | DMA_channel);
-
- /* Clear any data transfers that are currently executing. */
- outportb(ClearReg[DMA_channel], 0x00);
-
- /* Send the specified mode to the DMA. */
- outportb(ModeReg[DMA_channel], mode);
-
- /* Send the offset address. The first byte is the low base offset, the */
- /* second byte is the high offset. */
- outportb(AddrPort[DMA_channel], LOW_BYTE(blk->offset));
- outportb(AddrPort[DMA_channel], HI_BYTE(blk->offset));
-
- /* Send the physical page that the data lies on. */
- outportb(PagePort[DMA_channel], blk->page);
-
- /* Send the length of the data. Again, low byte first. */
- outportb(CountPort[DMA_channel], LOW_BYTE(blk->length));
- outportb(CountPort[DMA_channel], HI_BYTE(blk->length));
-
- /* Ok, we're done. Enable the DMA channel (clear the mask). */
- outportb(MaskReg[DMA_channel], DMA_channel);
-
- /* Re-enable interrupts before we leave. */
- enable();
- }
-
- void PauseDMA(uchar DMA_channel)
- {
- /* All we have to do is mask the DMA channel's bit on. */
- outportb(MaskReg[DMA_channel], 0x04 | DMA_channel);
- }
-
- void UnpauseDMA(uchar DMA_channel)
- {
- /* Simply clear the mask, and the DMA continues where it left off. */
- outportb(MaskReg[DMA_channel], DMA_channel);
- }
-
- void StopDMA(uchar DMA_channel)
- {
- /* We need to set the mask bit for this channel, and then clear the */
- /* selected channel. Then we can clear the mask. */
- outportb(MaskReg[DMA_channel], 0x04 | DMA_channel);
-
- /* Send the clear command. */
- outportb(ClearReg[DMA_channel], 0x00);
-
- /* And clear the mask. */
- outportb(MaskReg[DMA_channel], DMA_channel);
- }
-
- uint DMAComplete(uchar DMA_channel)
- {
- /* Register variables are compiled to use registers in C, not memory. */
- register int z;
-
- z = CountPort[DMA_channel];
- outportb(0x0C, 0xFF);
-
- /* This *MUST* be coded in Assembly! I've tried my hardest to get it */
- /* into C, and I've had no success. :( (Well, at least under Borland.) */
- redo:
- asm {
- mov dx,z
- in al,dx
- mov bl,al
- in al,dx
- mov bh,al
-
- in al,dx
- mov ah,al
- in al,dx
- xchg ah,al
-
- sub bx,ax
- cmp bx,40h
- jg redo
- cmp bx,0FFC0h
- jl redo
- }
- return _AX;
- }
-
- ────────────────────────────────────────────────────────────────────────────────
-
- I think all the above functions are self explanatory except for the last
- one. The last function returns the number of bytes that the DMA has
- transferred to (or read from) the device. I really don't know how it works
- as it's not my code. I found it laying on my drive, and I thought it might
- be somewhat useful to those of you out there. You can find out when a DMA
- transfer is complete this way if the I/O card doesn't raise an interrupt.
- DMAComplete() will return -1 (or 0xFFFF) if there is no DMA in progress.
-
- Don't forget to load the length into your DMA_block structure as well
- before you call StartDMA(). (When I was writing these routines, I forgot
- to do that myself... I was wondering why it was transferring garbage.. <G>)
-
- I hope you all have caught on to how the DMA works by now. Basically it
- keeps a list of DMA channels that are running or not. If you need to change
- something in one of these channels, you mask the channel, and reprogram. When
- you're done, you simply clear the mask, and the DMA starts up again.
-
- If anyone has problems getting this to work, I'll be happy to help. Send
- us mail at the address below, and either I or another Rage member will fix
- your problem(s).
-
- Enjoy!
- - Night Stalker
-
- -------------------------------------------------------------------------------
-
- Look for other Rage Technologies, Inc. stuff coming soon:
-
- - Our first major demo, "Transvectoring". The theme is to
- show off our new 3-D engine with lightsourcing and texture
- mapping... REALLY fast. Also to show what objects beyond
- 3D really look like. For example, a 4D or a 5D cube. Maybe
- more. Expected release date: Mid '95 (?)
-
- - Night Hawk 0.2α BBS. The first BBS software to show that
- ANSI is dead, and RIP is a thing of the past. Features
- include: True multitasking, full video and audio routines,
- and more. Expected release date: Early/Mid '96.
-
- -------------------------------------------------------------------------------
-
- Other news:
-
- - Shadow Lands is *NOT* up! Please don't call for a few weeks!
- Night Stalker is in the process of rebuilding his system and
- adding more disk space. We are hoping that Night Stalker will
- have Shadow Lands up by Mid-February.
-
- - Rage Technologies, Inc. has a mailing list. If you'd like to
- get ahold of any one of us, send E-mail to:
-
- ragetech@trappen.vsl.ist.ucf.edu
-
- - Rage Technologies, Inc. also has an experimental FTP server
- running. If you would like to get any Rage products, simply
- anonymous FTP to: trappen.vsl.ist.ucf.edu. All Rage files
- are located in /pub/ragetech.
-
-